package org.windowkit.toolkit.widget;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.ArrayList;

import javax.swing.BoundedRangeModel;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.DefaultFormatter;

import org.windowkit.toolkit.RegExFormatter;
import org.windowkit.toolkit.icons.ArrowIcon;
import org.windowkit.toolkit.icons.ArrowLineIcon;


/**
 *
 * @author Eric Lundin
 */
@SuppressWarnings("serial")
public class PageSelector extends JComponent implements
		ActionListener, ChangeListener, FocusListener {

	/**
	 * Action command for previous value button.
	 */
	protected static final String PREVIOUS_VAL = "PREVIOUS_VAL";

	/**
	 * Action command for next value button.
	 */
	protected static final String NEXT_VAL = "NEXT_VAL";

	/**
	 * Action command for first value button.
	 */
	protected static final String FIRST_VAL = "FIRST_VAL";

	/**
	 * Action command for last value button.
	 */
	protected static final String LAST_VAL = "LAST_VAL";

	/**
	 * Tool tip for previous value button.
	 */
	protected static final String PREVIOUS_VAL_TOOL_TIP = "View Previous Record";

	/**
	 * Tool tip for next value button.
	 */
	protected static final String NEXT_VAL_TOOL_TIP = "View Next Record ";

	/**
	 * Tool tip for first value button.
	 */
	protected static final String FIRST_VAL_TOOL_TIP = "View First Record";

	/**
	 * Tool tip for last value button.
	 */
	protected static final String LAST_VAL_TOOL_TIP = "View Last Record";

	/**
	 * Catches and redispatches events.
	 */
	protected EventDispatcher dispatcher;

	/**
	 * Holds the components which make up this control. Will be added to the
	 * component.
	 */
	protected JPanel panel;

	/**
	 * Model to track min, max, current position etc.
	 */
	protected BoundedRangeModel model;

	/**
	 * Button to jump to the first value.
	 */
	protected JButton firstButton;

	/**
	 * Button to jump to the last value.
	 */
	protected JButton lastButton;

	/**
	 * Button to choose the next value.
	 */
	protected JButton nextButton;

	/**
	 * Button to choose the previous value.
	 */
	protected JButton previousButton;

	/**
	 * TextField to display current number or allow user to type in their own.
	 */
	protected JFormattedTextField textField;

	/**
	 * Create a new control with an initial value equal to the minimum allowed
	 * value.
	 *
	 * @param min
	 *            The minimum value this control can have.
	 * @param max
	 *            The maximum value this control can have.
	 */
	public PageSelector(final int min, final int max) {
		this(min, max, min);
	}

	/**
	 * Create a new control.
	 *
	 * @param min
	 *            The minimum value this control can have.
	 * @param max
	 *            The maximum value this control can have.
	 * @param value
	 *            The initial value this control will have.
	 */
	public PageSelector(final int min, final int max, final int value) {
		super();
		init();
		initComponents();
		setModel(createModel(min, max, value));
	}

	/**
	 * Create a model with the given attribtes. Add a listener to the model.
	 *
	 * @param min
	 *            The minimum value the model can have.
	 * @param max
	 *            The maximum value the model can have.
	 * @param value
	 *            The initial value the model has.
	 * @return A BoundedRangeModel with the given attributes.
	 */
	private BoundedRangeModel createModel(final int min, final int max, final int value) {
		BoundedRangeModel brm = new DefaultBoundedRangeModel(value, 0, min, max);
		return brm;
	}

	/**
	 * Initialize any objects needed to support this class.
	 */
	public void init() {
		dispatcher = new EventDispatcher();
	}

	/**
	 * Initialize components.
	 */
	private void initComponents() {
		previousButton = new JButton(new ArrowIcon(SwingConstants.WEST));
		nextButton = new JButton(new ArrowIcon(SwingConstants.EAST));
		firstButton = new JButton(new ArrowLineIcon(SwingConstants.WEST));
		lastButton = new JButton(new ArrowLineIcon(SwingConstants.EAST));

		previousButton.setActionCommand(PREVIOUS_VAL);
		nextButton.setActionCommand(NEXT_VAL);
		firstButton.setActionCommand(FIRST_VAL);
		lastButton.setActionCommand(LAST_VAL);

		previousButton.addActionListener(this);
		nextButton.addActionListener(this);
		firstButton.addActionListener(this);
		lastButton.addActionListener(this);

		setToolTips(FIRST_VAL_TOOL_TIP, LAST_VAL_TOOL_TIP, NEXT_VAL_TOOL_TIP,
				PREVIOUS_VAL_TOOL_TIP);
		textField = new JFormattedTextField(
				new RegExFormatter("(^)|(\\d{1,5})"));
		((DefaultFormatter) textField.getFormatter()).setAllowsInvalid(false);
		textField.setColumns(4);
		textField.addActionListener(this);
		textField.addFocusListener(this);

		layoutComponents();

		// gbc.fill = GridBagConstraints.NONE;
		// add(panel,gbc);
	}

	private void layoutComponents() {

		// setLayout(new GridBagLayout());
		GridBagConstraints gbc = new GridBagConstraints();
		// gbc.fill = GridBagConstraints.VERTICAL;
		// gbc.weighty=1;
		// gbc.weightx=0;
		// gbc.gridx = 0;
		// add(firstButton, gbc);
		// gbc.gridx = GridBagConstraints.RELATIVE;
		// add(previousButton, gbc);
		// add(textField, gbc);
		// add(nextButton, gbc);
		// add(lastButton, gbc);
		JPanel b1 = new JPanel(new GridBagLayout());
		JPanel b2 = new JPanel(new GridBagLayout());

		gbc.fill = GridBagConstraints.VERTICAL;
		gbc.weighty = 1;
		gbc.gridx = 0;
		b1.add(firstButton, gbc);
		gbc.gridx = 1;
		b1.add(previousButton, gbc);

		gbc = new GridBagConstraints();
		gbc.fill = GridBagConstraints.VERTICAL;
		gbc.weighty = 1;
		gbc.gridx = 0;
		b2.add(nextButton, gbc);
		gbc.gridx = 1;
		b2.add(lastButton, gbc);

		setLayout(new GridBagLayout());
		gbc = new GridBagConstraints();
		gbc.fill = GridBagConstraints.NONE;
		add(b1, gbc);
		gbc.weightx = 1;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		add(textField, gbc);
		gbc.weightx = 0;
		gbc.fill = GridBagConstraints.NONE;
		add(b2, gbc);
		setButtonSizes();
	}

	protected void setButtonSizes() {
		Dimension dim = textField.getPreferredSize();
		dim.width = (int) (firstButton.getPreferredSize().width / 1.5);
		firstButton.setPreferredSize(dim);
		previousButton.setPreferredSize(dim);
		nextButton.setPreferredSize(dim);
		lastButton.setPreferredSize(dim);
	}

	/**
	 * Set custom tool tips on the buttons.
	 *
	 * @param first
	 *            First button tool tip.
	 * @param last
	 *            Last button tool tip.
	 * @param next
	 *            Next button tool tip.
	 * @param previous
	 *            Previous button tool tip.
	 */
	public void setToolTips(final String first, final String last, final String next,
			final String previous) {
		firstButton.setToolTipText("<html>" + first + "</html>");
		lastButton.setToolTipText("<html>" + last + "</html>");
		nextButton.setToolTipText("<html>" + next + "</html>");
		previousButton.setToolTipText("<html>" + previous + "</html>");
	}

	/**
	 * Set the model for this control to the given model.
	 *
	 * @param brm
	 *            The model to use.
	 */
	public void setModel(final BoundedRangeModel brm) {
		if (model != null) {
			model.removeChangeListener(dispatcher);
			model.removeChangeListener(this);
		}
		model = brm;
		model.addChangeListener(this);
		model.addChangeListener(dispatcher);
		textField.setText(String.valueOf(model.getValue()));
	}

	/**
	 * Add a change listener.
	 *
	 * @param listener
	 *            The ChangeListener to add.
	 */
	public void addChangeListener(final ChangeListener listener) {
		dispatcher.addChangeListener(listener);
	}

	/**
	 * Remove a change listener.
	 *
	 * @param listener
	 *            The ChangeListener to remove.
	 */
	public void removeChangeListener(final ChangeListener listener) {
		dispatcher.removeChangeListener(listener);
	}

	/**
	 * Set the value of the control. Will display the model value if the given
	 * string is not a valid number.
	 *
	 * @param v
	 *            The string representing the value for this control.
	 */
	public void setValue(final String v) {
		if (v.matches("\\d{1,5}")) {
			setValue(Integer.valueOf(v));
		} else {
			textField.setText(String.valueOf(model.getValue()));
		}
	}

	public void actionPerformed(final ActionEvent e) {
		String actionCommand = e.getActionCommand();
		if (actionCommand.equals(PREVIOUS_VAL)) {
			model.setValue(model.getValue() - 1);
		} else if (actionCommand.equals(NEXT_VAL)) {
			model.setValue(model.getValue() + 1);
		} else if (actionCommand.equals(FIRST_VAL)) {
			model.setValue(model.getMinimum());
		} else if (actionCommand.equals(LAST_VAL)) {
			model.setValue(model.getMaximum());
		} else if (e.getSource() == textField) {
			setValue(textField.getText());
		}
	}

	/**
	 * This class will catch and redispatch any ChangeEvents so they appear to
	 * come from the wrapper class and not the wrapped class.
	 *
	 * @author Eric Lundin
	 */
	private class EventDispatcher implements ChangeListener {

		/**
		 * The listeners for our slider.
		 */
		ArrayList<ChangeListener> listeners;

		public EventDispatcher() {
			listeners = new ArrayList<ChangeListener>();
		}

		/**
		 * Add the given ChangeListener.
		 *
		 * @param listener
		 *            The ChangeListener to add.
		 */
		public void addChangeListener(final ChangeListener listener) {
			if (listener != null) {
				listeners.add(listener);
			}
		}

		/**
		 * Remove the given ChangeListener.
		 *
		 * @param listener
		 *            The ChangeListener to remove.
		 */
		public void removeChangeListener(final ChangeListener listener) {
			listeners.remove(listener);
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent)
		 */
		@SuppressWarnings("unused")
		public void stateChanged(final ChangeEvent e) {
			ChangeEvent newEvent;
			newEvent = new ChangeEvent(PageSelector.this);
			for (ChangeListener current : listeners) {
				current.stateChanged(newEvent);
			}
		}

	}

	/**
	 * Get the extent of this control's model.
	 *
	 * @return The extent of the model.
	 */
	public int getExtent() {
		return model.getExtent();
	}

	/**
	 * Get the maximum value this model will allow.
	 *
	 * @return The model's maximum allowed value.
	 */
	public int getMaximum() {
		return model.getMaximum();
	}

	/**
	 * Get the minimum value this model will allow.
	 *
	 * @return The model's minimum allowed value.
	 */
	public int getMinimum() {
		return model.getMinimum();
	}

	/**
	 * Get whether the value is currently adjusting.
	 *
	 * @return True if the value of the model is currently changing. False
	 *         otherwise.
	 */
	public boolean getValueIsAdjusting() {
		return model.getValueIsAdjusting();
	}

	/**
	 * Set the extent of the model.
	 *
	 * @param extent
	 *            The new extent for the model.
	 */
	public void setExtent(final int extent) {
		model.setExtent(extent);
	}

	/**
	 * Set the maximum value the model allows.
	 *
	 * @param maximum
	 *            The maximum value the model will allow.
	 */
	public void setMaximum(final int maximum) {
		model.setMaximum(maximum);
	}

	/**
	 * Set the minimum value the model will allow.
	 *
	 * @param minimum
	 *            The minimum value the model will allow.
	 */
	public void setMinimum(final int minimum) {
		model.setMinimum(minimum);
	}

	/**
	 * Set the value of the model.
	 *
	 * @param n
	 *            The value the model will hold.
	 */
	public void setValue(final int n) {
		model.setValue(n);
	}

	/**
	 * Sets whether the model value is adjusting.
	 *
	 * @param b
	 *            True if the model is adjusting, false otherwise.
	 */
	public void setValueIsAdjusting(final boolean b) {
		model.setValueIsAdjusting(b);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent)
	 */
	@SuppressWarnings("unused")
	public void stateChanged(final ChangeEvent e) {
		if (!model.getValueIsAdjusting()) {
			textField.setText(String.valueOf(model.getValue()));
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
	 */
	@SuppressWarnings("unused")
	public void focusGained(final FocusEvent e) {
		// Do nothing
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
	 */
	public void focusLost(final FocusEvent e) {
		if (e.getSource() == textField) {
			setValue(textField.getText());
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.bmi.foundation.core.Preferenced#clearValue()
	 */
	public void clearValue() {
		setValue(model.getMinimum());
	}

}
